#include <string.h>
#include <stdlib.h>
#include "crypto/base64.h"
#include "crypto/errors.h"
#include "crypto/types.h"

static void initDecodeArray(void)
{
    for (int i = 0; i < BASE64_LENGTH; i++)
    {
        DECODE_ARRAY[ENCODE_ARRAY[i]] = (int8)i;
    }
}

static int8 getDecodeNumber(int8 ch)
{
    if (DECODE_ARRAY['B'] == 0)
    {
        initDecodeArray();
    }
    return DECODE_ARRAY[ch];
}

int encodeBase64(const SAFE_array *res, SAFE_array **dst)
{
    if (res->size > res->cap)
    {
        export(CODE_SAFE_ARRAY_ERROR, "");
    }

    int groups = res->size / 3;
    int left = res->size % 3;
    int needDstCap = groups * 4;

    int index = 0;
    int base64ArrayIndex = 0;

    if (left != 0)
    {
        needDstCap += 4;
    }

    if (*dst == NULL)
    {
        *dst = (SAFE_array *)malloc(offset(SAFE_array, data) + sizeof(byte) * needDstCap);
        if (*dst == NULL)
        {
            export(CODE_SAFE_ARRAY_ERROR, "");
        }
        (*dst)->cap = needDstCap;
    }
    else
    {
        if ((*dst)->cap < needDstCap)
        {
            export(CODE_SAFE_ARRAY_ERROR, "");
        }
    }

    for (int i = 0; i < groups; i++)
    {
        base64ArrayIndex = res->data[i * 3] >> 2;
        (*dst)->data[index++] = ENCODE_ARRAY[base64ArrayIndex];
        base64ArrayIndex = ((res->data[i * 3] & 3) << 4) + (res->data[i * 3 + 1] >> 4);
        (*dst)->data[index++] = ENCODE_ARRAY[base64ArrayIndex];
        base64ArrayIndex = ((res->data[i * 3 + 1] & 15) << 2) + (res->data[i * 3 + 2] >> 6);
        (*dst)->data[index++] = ENCODE_ARRAY[base64ArrayIndex];
        base64ArrayIndex = res->data[i * 3 + 2] & 63;
        (*dst)->data[index++] = ENCODE_ARRAY[base64ArrayIndex];
    }

    if (left == 1)
    {
        base64ArrayIndex = res->data[groups * 3] >> 2;
        (*dst)->data[index++] = ENCODE_ARRAY[base64ArrayIndex];
        base64ArrayIndex = (res->data[groups * 3] & 3) << 4;
        (*dst)->data[index++] = ENCODE_ARRAY[base64ArrayIndex];
        (*dst)->data[index++] = FIXED_SIGN;
        (*dst)->data[index++] = FIXED_SIGN;
    }
    else if (left == 2)
    {
        base64ArrayIndex = res->data[groups * 3] >> 2;
        (*dst)->data[index++] = ENCODE_ARRAY[base64ArrayIndex];
        base64ArrayIndex = ((res->data[groups * 3] & 3) << 4) + (res->data[groups * 3 + 1] >> 4);
        (*dst)->data[index++] = ENCODE_ARRAY[base64ArrayIndex];
        base64ArrayIndex = (res->data[groups * 3 + 1] & 15) << 2;
        (*dst)->data[index++] = ENCODE_ARRAY[base64ArrayIndex];
        (*dst)->data[index++] = FIXED_SIGN;
    }

    (*dst)->size = index;

    return CODE_SUCCESS;
}

int decodeBase64(const SAFE_array *res, SAFE_array **dst)
{
    if (res->size > res->cap)
    {
        export(CODE_SAFE_ARRAY_ERROR, "");
    }

    int groups = res->size / 4;

    int resIndex = res->size - 1;
    int left = 0;
    int needDstCap = 0;

    int dstIndex = 0;
    int dstValue = 0;
    int8 a, b;

    if (res->data[resIndex] == '=')
    {
        left++;
        if (res->data[resIndex - 1] == '=')
        {
            left++;
        }
    }

    needDstCap = groups * 3 - left;

    if (*dst == NULL)
    {
        *dst = (SAFE_array *)malloc(offset(SAFE_array, data) + sizeof(byte) * needDstCap);
        if (*dst == NULL)
        {
            export(CODE_SAFE_ARRAY_ERROR, "");
        }
        (*dst)->cap = needDstCap;
    }
    else
    {
        if ((*dst)->cap < needDstCap)
        {
            export(CODE_SAFE_ARRAY_ERROR, "");
        }
    }

    if (left != 0)
    {
        groups -= 1;
    }

    for (int i = 0; i < groups; i++)
    {
        a = getDecodeNumber(res->data[i * 4]);
        b = getDecodeNumber(res->data[i * 4 + 1]);
        dstValue = (a << 2) + (b >> 4);
        (*dst)->data[dstIndex++] = dstValue;

        a = getDecodeNumber(res->data[i * 4 + 2]);
        dstValue = ((b & 15) << 4) + (a >> 2);
        (*dst)->data[dstIndex++] = dstValue;

        b = getDecodeNumber(res->data[i * 4 + 3]);
        dstValue = ((a & 3) << 6) + b;
        (*dst)->data[dstIndex++] = dstValue;
    }

    if (left == 1)
    {
        a = getDecodeNumber(res->data[groups * 4]);
        b = getDecodeNumber(res->data[groups * 4 + 1]);
        dstValue = (a << 2) + (b >> 4);
        (*dst)->data[dstIndex++] = dstValue;

        a = res->data[groups * 4 + 2];
        dstValue = ((b & 15) << 4) + (a >> 4);
        (*dst)->data[dstIndex++] = dstValue;
    }
    else if (left == 2)
    {
        dstValue = (getDecodeNumber(res->data[groups * 4]) << 2) + (getDecodeNumber(res->data[groups * 4 + 1]) >> 4);
        (*dst)->data[dstIndex++] = dstValue;
    }

    (*dst)->size = dstIndex;

    return CODE_SUCCESS;
}